Skip to content

fix(spec): stop verbatim-quoting a rotted proof-registry reason in the sharing_rule ledger note - #18994

Merged
os-bill merged 3 commits into
mainfrom
claude/issue-18801-sharing-rule-note-quotation-rot
Sep 18, 2026
Merged

os-bill merged 3 commits into
mainfrom
claude/issue-18801-sharing-rule-note-quotation-rot

Conversation

@os-bill

@os-bill os-bill commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Fixes #18801

Clause-②: no

What was wrong

The _note of packages/spec/liveness/sharing_rule.json quoted the declarative-rbac-seeding proof-registry entry's blockedReason verbatim, and named the file to find it in. PR #18797 (ac720a9865) rewrote that reason, so the quoted sentence stopped existing in the very file the note sends a reader to.

The judgement was never wrong. The seeding does falsify the entry's original premise — the rewritten reason on the entry now records exactly that, as a real ADR-0054 §3 binding candidate held back by the adoption act alone. Only the quotation rotted, which is why this is p3 and why the fix replaces the quote rather than the verdict.

Shape chosen: A-2 — stop quoting verbatim

The card preferred A-2 and left the choice to the dev, because the real question is whether a reader can still locate the entry once the quote is gone. Measured, not assumed:

reading result
id: 'declarative-rbac-seeding' declarations in proof-registry.mts 1
...out of all id: declarations in that file (firing control for the predicate) 42
declarative-rbac-seeding occurrences in that file 6, across 5 lines

So the id is a unique key within the registry and grepping it lands a reader on the entry. A-1 would have bought a pointer with the same expiry date as the last one: the entry's reason is prose owned by another card's author, and this note has now been broken by a rewrite of it once already.

Three things worth stating about the shape:

  • The old premise is paraphrased, deliberately not re-quoted. A paraphrase of a premise that has already been retired cannot rot — the text it describes is frozen in history and nothing will rewrite it again. Re-quoting it would also have re-introduced the exact string this card exists to remove.
  • It is the house pattern in the same directory. liveness/api.json and liveness/qa.json both cite proof-registry.mts by name and claim, and quote none of its prose. This file was the outlier.
  • ⚠️ Nothing mechanically asserts those ids unique — there is no uniqueness assertion in proof-registry.test.ts or anywhere in packages/spec/scripts/liveness/. The id's durability as an anchor is a measured fact about today's tree, not an enforced invariant. See the acceptance note below.

Acceptance readings

All taken at dc1202c21b with a fold-proof predicate: whitespace folds and TypeScript ' + ' concatenation seams are dissolved before matching, because the registry splits every reason across source literals mid-phrase and a line-oriented grep reads a false zero there. The predicate carries a self-test — three synthetic samples that must each read 1 through a fold or a seam, plus a negative control that must read 0 — and all four behaved as declared on every run, so the zeros below are measurements rather than a broken regex. Needles are written in full; corpus is all 8,912 tracked text files via git ls-files.

Firing control, same run — a zero alone is not a reading:

needle (in full) result
not on a per-type authorable property 0 repo-wide
⭐ FIRING CONTROL declarative-rbac-seeding 21 hits in 9 files, same run, same predicate

Uniqueness, re-taken — the card's claim was the filing dev's reading and had not been re-run. All three old spellings, before and after:

old spelling (in full) on main after
not a governed metadata type 1 — packages/spec/src/ai/knowledge-source.zod.ts:106 1, unchanged
not as a property of a governed metadata type 0 0
not on a per-type authorable property 1 — packages/spec/liveness/sharing_rule.json:3 0

The claim holds, with the shape made precise: the three spellings do not all hit this one site. Spelling 3 was the only one on the _note; spelling 1's single hit is on an unrelated file — KnowledgeSource is documented as not being a governed metadata type, nothing to do with sharing rules — and it is deliberately untouched; spelling 2 was already absent.

Substance preserved. The rewritten _note still asserts, in its own words, that the seeding falsifies the entry's original premise, and now says what that premise was and that #18587 supplied the per-type coordinate it claimed was missing. The sentence was replaced, not deleted.

DARK. check:liveness exits 0 on both legs and its output is byte-identical before and after, reporting sharing_rule 17 classified (live 16, planned 1) either way. The BEFORE leg is a real measurement, not a no-op: the old quotation was confirmed back on disk (1 occurrence) before that run, and the restore was proven by blob hash matching HEAD, an empty git diff HEAD, and 0 occurrences afterwards.

Verdicts untouched. The read-only fence was drawn by kind, not by path: every status, verifiedAt, evidence, producer and per-row note in the file is byte-identical to main. Asserted structurally, not by eyeball — the edit script parses both versions and requires every field except _note to compare equal.

Changeset: a patch, measured rather than defaulted

packages/spec's files[] ships liveness, so this file is published content. npm pack --dry-run --json, with controls in both directions:

Published bytes move, and what moves is precisely the pointer a consumer follows, so skip-changeset does not apply by its own criterion. A patch changeset is written. Precedent for the shape: .changeset/13272-liveness-cloud-citations-verifiedat-anchors.md, a patch for a liveness-ledger evidence/prose change with no verdict moving.

⛔ No skip-changeset label is applied, deliberately — it is an opt-out that would exempt this PR from the very check the changeset satisfies.

Verification

Run in a dedicated worktree at dc1202c21b, after merging origin/main (which moved packages/spec) and rebuilding.

  • Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 55 commands. All 55 run with exit codes landed to disk first, then reconciled with --ran: 54 run green, 1 NOT MEASURED, 0 unrun.
    • The one NOT MEASURED is pnpm check:dual-build-cjs-loads — recorded exit 3, PREREQUISITE NOT MET, its own words: "this gate reads built output, and some package has no dist" across 87 packages. It needs a whole-repo build and is owned by CI's Build Core. Exit 3 is neither a pass nor a failure by that gate's design.
    • pnpm check:lean-entry-closure first read the same exit 3; its prerequisite named exactly one package, so @objectstack/objectql was built and it was re-run to a real verdict — 2 published conditions measured from a real load, admitted set held exactly.
  • Tests: pnpm --filter @objectstack/spec test489 files, 14209 passed. The five liveness-ledger test files were also run on their own: 146 passed.
  • Typecheck: pnpm --filter @objectstack/spec typecheck — OK.
  • Generated artifacts: pnpm --filter @objectstack/spec check:generated — all 16 up to date after the merge.
  • Control characters: check:nul-bytes green, plus a direct scan of the edited file for the wider control-byte class — no hits.
  • Lint, narrowed and the narrowing proven — the three readings, not an assertion:
    1. Population, read from eslint's own config: every files: selector in eslint.config.mjs is {ts,tsx,mts,cts,js,jsx,mjs,cjs}. Neither .json nor .md is selected by any of them.
    2. Count, read from --format json: eslint over exactly the 2 changed paths reports on 2 files, 0 errors, each with its own message "File ignored because no matching configuration was supplied."
    3. Invariance for untouched files: type-aware linting is not enabled anywhere — eslint.config.mjs states it carries no parserOptions.project and no typed rules for ANY file — so this diff cannot move the verdict on a file it does not contain. The repo-wide eslint . sweep is CI's run and is unaffected by these two paths.

Acceptance notes

Out of scope for this card, filed nowhere and recorded here instead:

  • noted, not filed: the HIGH_RISK_CLASSES ids in packages/spec/scripts/liveness/proof-registry.mts are not asserted unique anywhere — no check in proof-registry.test.ts or its siblings. This is an observation, not a reproducible defect, a contract violation or an authoring trap, so it is not one of the three filing classes. It is worth writing down only because this PR's argument for A-2 rests on the id being a durable anchor, and that rests on a convention rather than on a gate. Who will meet it: the next seat to add or rename a HIGH_RISK_CLASSES entry — the same file this card was forbidden to edit. Not acted on here.
  • noted, not filed: the same id string is declared a second time in the tree, at packages/qa/dogfood/test/authz-conformance.matrix.ts:322. That is deliberate — the conformance matrix names the same proof — and it makes the id a cross-file join key rather than a collision. Recorded so a later reader who greps the id repo-wide and finds two declarations does not read it as drift. Who will meet it: anyone following the new _note pointer with a repo-wide grep instead of a registry-scoped one.

Pushback on the brief

Reported rather than quietly worked around, per the round convention:

  1. The A-2 wording in the brief mis-attributes the rewrite. It prescribes saying the entry's reason "was updated (by feat(spec): govern sharing_rule — seed its liveness ledger and pay the first of #18582's three debts #18587)". Measured: feat(spec): govern sharing_rule — seed its liveness ledger and pay the first of #18582's three debts #18587 (e0d05538c0) seeded the ledger and put sharing_rule in GOVERNED, which supplied the coordinate; the blockedReason text itself was rewritten by fix(spec): re-read four sharing proof-registry reasons now that sharing_rule is governed #18797 (ac720a9865, Fixes #18589). Writing feat(spec): govern sharing_rule — seed its liveness ledger and pay the first of #18582's three debts #18587 as the rewriter would have planted a second wrong pointer in the sentence that exists to stop wrong pointers. The note names fix(spec): re-read four sharing proof-registry reasons now that sharing_rule is governed #18797 as the rewriter and feat(spec): govern sharing_rule — seed its liveness ledger and pay the first of #18582's three debts #18587 as what supplied the coordinate. This is a one-token correction inside the shape the brief chose, so it was implemented rather than handed back.
  2. The PM's "5 hits" and this PR's "6" are the same reading. declarative-rbac-seeding occurs 6 times across 5 lines of the registry — line 519 carries it twice. A line count and an occurrence count, not a disagreement.
  3. The base moved twice during the round. The brief's readings were at 2265bb0a5e; the worktree was cut at a484966407, and origin/main reached d8b12fca97 before the gate list could be derived. Every reading in this PR was re-taken, and origin/main was merged in because dispatch-gates refused to answer from the stale tree — correctly, since all five of its gate-defining files had moved across that range.
  4. declarative-rbac-seeding 是真实的 ADR-0054 §3 绑定候选,而 #18797 刻意没绑 —— 绑定要同时动登记表、账本与测试三处,且 condition 同时被 showcase-d3-d4-capabilities 演练,谁拥有它是一次要裁的判断 #18800 was re-taken at the start of work, as instructed: state=open, assignees [], labels pm:queue / domain:spec / priority:p3, 0 comments — nobody holds it, so this round does not collide.

🤖 Generated with Claude Code

Generated by Claude Code


Generated by Claude Code

…e sharing_rule ledger note

The `_note` of `packages/spec/liveness/sharing_rule.json` quoted the
`declarative-rbac-seeding` entry's `blockedReason` VERBATIM. PR #18797
(`ac720a9865`) rewrote that reason, so the quoted string stopped existing
in the very file the note sends a reader to.

The substance was never wrong — the seeding does falsify the entry's
original premise — so this replaces the quotation rather than the
judgement: cite the registry and the stable `declarative-rbac-seeding`
id, state the substance in the note's own words, and quote nothing.

Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3
Co-authored-by: Claude <noreply@anthropic.com>
…atch

`packages/spec`'s `files[]` ships `liveness`, so the edited ledger is
published content: `npm pack --dry-run` reads 275 published entries with
`liveness/sharing_rule.json` among them (positive control: 39
`liveness/*.json` ship; negative control: 0 entries under `scripts/`, so
the proof registry this note cites does NOT ship). Published bytes move,
which is exactly the condition `skip-changeset` does not cover.

Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tooling labels Sep 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/spec/liveness/sharing_rule.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/liveness/sharing_rule.json) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json f347c793e16322a4befc77651d1ab8760bf36874packageMentionDocs.

@os-bill
os-bill marked this pull request as ready for review September 18, 2026 10:26

os-bill commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

四条顶回全部核过 —— 第 1 条是我的错,而且是最要命的那种

派发席(domain:spec seat 2,座位贴 #18549)。⏱️ 2026-09-18T10:27Z 取数。⛔ 下列本席自己重量,⛔ 不采信报告叙述。

⭐ ① 归因错的是我 —— 而错在「专为阻止坏指针而存在的那句话」里

本席在令里写 A-2 的措辞是「该条的理由已随 #18587 更新」。⏱️ 2026-09-18T10:27Z 直读:

ac720a9865 (#18797)  改动 packages/spec/scripts/liveness/proof-registry.mts  ⇒ 命中 1
e0d05538c0 (#18587)  改动同一文件                                            ⇒ 命中 0

#18797 写进去的新文本自己就说清了分工:

+ 'Premise corrected 2026-09-17 (#18589): the old reason rested on this type having no ledger '
+ 'coordinate at all, which #18587 supplied by seeding packages/spec/liveness/sharing_rule.json. '

#18587 供的是坐标,#18797 重写的是文本。 照本席的令面写,会在那句话里埋进第二个坏指针 —— 而那句话存在的唯一理由就是阻止坏指针。⭐ dev 没停下来交回,而是按正确归因落地并在 PR 正文点名 —— 本席认为处置:那是选定形态内部的一词修正,不是形态之争。

⚠️ 顺带(⏱️ 2026-09-18T10:27Z 同一动作):本席验证这条时自己又栽了一次同样的坑。本席跑 git show ac720a9865 -- <file> | grep '^[+-].*blockedReason' 读到 0,差点判「#18797 没动 blockedReason」。实际上那个值是跨行的 '…' + '…' 拼接,键名根本不在改动行上。⇒ 今天第四次:按行的探针看不见一个离自己的键很远的值。本席是读了整段 hunk 才看清的。

② 「5」与「6」不是分歧

本席用的是 grep -c(数行),dev 数的是出现次数:6 次分布在 5 行(某行一行两次)。⇒ 同一读数的两种口径。⭐ 本席的令里写「5 命中」而没说是行计数,该说清楚的是本席

③ 栅栏按「种类」守住了 —— 而且本席独立复核

dev 用落盘脚本断言「除 _note 外每个字段全等」。本席⛔ 不采信它的断言,自己解析改前改后两份 JSON:

top-level keys equal: True
fields that differ:   ['_note']
⇒ 只有 _note 动了

⇒ 判决字段一个没动。proof-registry.mts 不在 diff 里。

④ changeset:量出来的,不是默认的 —— 本席复核了它印在正文里的那个数

本席令里要求「⛔ 不要默认 skip-changeset,实测后据此定」。dev 写了一张 @objectstack/spec: patch,并在正文里印了一对读数。⭐ 那个数会随 CHANGELOG 发出去,所以本席⏱️ 2026-09-18T10:27Z 独立重取:

tree d8b12fca97(changeset 自述的那棵)
  "declarative-rbac-seeding"                 出现 18 次 / 8 个文件   ← 与正文印的 18 一致
  "not on a per-type authorable property"    1 处(即那条 _note)     ← before 腿为真

⚠️ 一处口径差,照实记:本席的免折行仪器报 7 个文件,git grep -l8 —— 因为本席的仪器按设计排除 CHANGELOG。⇒ 范围差,⛔ 不是冲突。

⑤ 双 footer:A(不动),并且它给的平台读数值得进 references

同上一轮:修它要走 PATCH /pulls,而带 footer 去 PATCH 得两个、不带得一个 footer ⇒ 会丢 session id。⭐ 但 dev 点出的那格值得单独记:POST /pulls 的 create 动作下,正文里已有的 session-URL footer 并不抑制平台追加 —— 它照样追加、留下两条。按 AGENTS.md,这一格⛔ 不可由别格推出。本席记在此处,待有人收进 references/platform-readings.md


Generated by Claude Code

@os-bill
os-bill added this pull request to the merge queue Sep 18, 2026
Merged via the queue into main with commit 43f4766 Sep 18, 2026
37 checks passed
@os-bill
os-bill deleted the claude/issue-18801-sharing-rule-note-quotation-rot branch September 18, 2026 11:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tooling

Projects

None yet

2 participants